﻿using System;
using System.Diagnostics;
using System.IO;
using Csu.Modsim.ModsimModel;
using Csu.Modsim.ModsimIO;
using ASquared.ModelOptimization;

namespace iwpm.optimization
{
    public class HydroScenario : IScenarioSolvable
    {
        #region Static variables

        public readonly static char Delim = '\t';
        public readonly static char[] Delims = new char[] { Delim };
        public readonly static string Headers
            = "Scenario_Name" + Delim
            + "Hydropower_Weight" + Delim
            + "Tolerance" + Delim
            + "Max_Iterations" + Delim
            + "Input XY File" + Delim
            + "Output Directory" + Delim
            + "Scenarios_Import_File"; 

        #endregion

        #region Instance variables

        private string _name, _newxyfile, _oldxyfile, _scenfile, _debugFile = "";
        private Stopwatch _sWatch = new Stopwatch();
        private OptiModel _optModel = null;
        private ConditionalGradientSolver _solver = null;
        private HydroTargetSeeker _objective;
        private StreamWriter _sw = null;
        private bool _error = false;
        private Model _model;

        #endregion

        #region Properties

        /// <summary>Gets and sets the name of this hydropower scenario.</summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }

        /// <summary>Gets the file from which new values were imported into the model.</summary>
        public string ScenarioImportsFile
        {
            get
            {
                return _scenfile;
            }
        }

        /// <summary>Gets the MODSIM model file path for this instance</summary>
        public string ModelFilePath
        {
            get
            {
                return _newxyfile;
            }
        }

        /// <summary>Sets the log file path for this scenario </summary>
        public string LogFilePath
        {
            set
            {
                try
                {
                    _sw = new StreamWriter(value);
                    string[] hydroVars = Array.ConvertAll(_objective.Targets, target => target.Name);
                    string otherCols = string.Join("_Simulated,", hydroVars) + "_Simulated,";
                    otherCols += string.Join("_Target,", hydroVars) + "_Target";
                    _sw.WriteLine("Date,Optimum,TotalOptimum,Time_Sec," + otherCols);
                }
                catch
                {
                    _sw = null;
                }
            }
        }

        /// <summary>Gets and sets the debug file path. If empty, the debug file isn't written.</summary>
        public string DebugFilePath
        {
            get
            {
                return _debugFile;
            }
            set
            {
                if (value != null && value != "")
                {
                    string basename = Path.GetDirectoryName(Path.GetFullPath(value)) + "\\"
                        + Path.GetFileNameWithoutExtension(value); 
                    _optModel.Debug(_debugFile = value);
#if DEBUG
                    _solver.LogFile = basename + "log_CGM.txt";
#endif
                }
                else
                    _solver.LogFile = "";
            }
        }

        /// <summary>Gets the solver used to optimize this instance.</summary>
        public ConditionalGradientSolver Solver
        {
            get
            {
                return _solver;
            }
        }

        /// <summary>Gets the model that handles the optimization.</summary>
        public OptiModel Model
        {
            get
            {
                return _optModel;
            }
        }

        /// <summary>Gets and sets the tolerance of the solver.</summary>
        public double Tolerance
        {
            get
            {
                return _solver.Tolerance;
            }
            set
            {
                _solver.Tolerance = value;
            }
        }

        /// <summary>Gets and sets the maximum iterations before the solver exits.</summary>
        public int MaxIterations
        {
            get
            {
                return _solver.MaxIterations;
            }
            set
            {
                _solver.MaxIterations = value;
                _optModel.MODSIMModel.maxit = value;
            }
        }

        /// <summary>Gets and sets the hydropower weight within the objective function.</summary>
        public double HydropowerWeight
        {
            get
            {
                return Convert.ToDouble(_optModel.Parameters);
            }
            set
            {
                _optModel.Parameters = value;
            }
        }

        /// <summary>Gets the time the algorithm to solve the model.</summary>
        public TimeSpan SolutionTime
        {
            get
            {
                return _sWatch.Elapsed;
            }
        }

        /// <summary>Gets whether an error occurred while running the scenario</summary>
        public bool ErrorOccurred
        {
            get
            {
                return _error; 
            }
        }

        #endregion

        #region Constructors

        public HydroScenario(string name, string oldxyfile, string newxyfile, double hydropowerWeight, double tolerance, int maxIterations)
            : this(name, oldxyfile, newxyfile, hydropowerWeight, tolerance, maxIterations, "")
        {
        }
        public HydroScenario(string name, string oldxyfile, string newxyfile, double hydropowerWeight, double tolerance, int maxIterations, string scenarioImportsFile)
        {
            this.Setup(name, oldxyfile, newxyfile, hydropowerWeight, tolerance, maxIterations, scenarioImportsFile); 
        }

        public void Setup(string name, string oldxyfile, string newxyfile, double hydropowerWeight, double tolerance, int maxIterations, string scenarioImportsFile)
        {
            // Build instances of OptiModel and ConditionalGradientSolver
            _name = name; 
            _newxyfile = newxyfile;
            _oldxyfile = oldxyfile;
            _scenfile = scenarioImportsFile; 

            // read old model 
            _model = new Model();
            XYFileReader.Read(_model, oldxyfile);

            // Import new data 
            if (scenarioImportsFile != "")
            {
                DataImporter importer = new DataImporter(_model, scenarioImportsFile);
                importer.Import(); 
            }

            // Make sure the output directory exists
            string dir = Path.GetDirectoryName(newxyfile);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            // Write the model to a new file
            XYFileWriter.Write(_model, newxyfile, XYFileWriter.CompressionLevelTypes.Full, OutputVersionType.LatestVersion);

            // build OptiModel
            _optModel = new OptiModel(newxyfile, ObjectiveFormulation.HydroTargets, 1.0);
            _model = _optModel.MODSIMModel; 
            _model.OnMessage += new Model.OnMessageDelegate(MODSIMModel_OnMessage);
            _model.OnModsimError += new Model.OnModsimErrorDelegate(MODSIMModel_OnModsimError);
            _model.maxit = maxIterations;
            _optModel.Parameters = hydropowerWeight;
            _objective = (HydroTargetSeeker)_optModel.objective;

            // build solver
            _solver = new ConditionalGradientSolver(_optModel);
            _solver.Converged += new EventHandler(_solver_Converged);
            _solver.Tolerance = tolerance;
            _solver.MaxIterations = maxIterations;
        }

        #endregion

        #region Methods

        void MODSIMModel_OnModsimError(string message)
        {
            _error = true; 
            Console.WriteLine(message); 
        }

        void MODSIMModel_OnMessage(string message)
        {
            Console.WriteLine(message); 
        }

        // event handlers 
        void _solver_Converged(object sender, EventArgs e)
        {
            _writeLog();
        }

        // solve 
        /// <summary>Solves a particular scenario with a specified hydropower weight, solver tolerance and max iterations.</summary>
        /// <param name="hydropowerWeight">The weight associated with meeting hydropower targets.</param>
        /// <param name="tolerance">The tolerance associated with the solver.</param>
        /// <param name="maxIterations">The maximum number iterations before the solver forces an exit.</param>
        public void Solve()
        {
            try
            {
                _sWatch.Reset();
                _sWatch.Start();
                _solver.Solve();
                _sWatch.Stop();
            }
            catch (Exception ex)
            {
                _optModel.MODSIMModel.FireOnError(ex.ToString()); 
                _sWatch.Reset();
            }
            finally
            {
                try
                {
                    _sw.Close();
                }
                catch { }
                finally
                {
                    _sw = null;
                }
            }
        }

        // write log 
        private void _writeLog()
        {
            string line;
            if (_sw == null)
                return;

            HydropowerTarget[] hydroTargets = _objective.Targets;
            string otherCols = string.Join(",",
                Array.ConvertAll(hydroTargets, hydroTarget => hydroTarget.Energy.ToString())) + ",";
            otherCols += string.Join(",",
                Array.ConvertAll(hydroTargets, hydroTarget => hydroTarget.EnergyTarget.ToString()));

            line = _optModel.MODSIMModel.mInfo.CurrentBegOfPeriodDate + ","
                + _solver.Optimum.ToString() + ","
                + _solver.TotalOptimum.ToString() + ","
                + this.SolutionTime.TotalSeconds + ","
                + otherCols;
            _sw.WriteLine(line);
        }

        #endregion

        #region Static methods 

        public static HydroScenario FromString(string s)
        {
            try
            {
                //   name, hydropowerWeight, tolerance, and maxIterations
                string[] headerSplit = Headers.Split(HydroScenario.Delims); 
                string[] args = s.Split(HydroScenario.Delims);
                if (args.Length < headerSplit.Length - 1 || headerSplit.Length < args.Length)
                    return null;

                // Get the input file and output directory 
                string oldxyfile = args[4];
                string outdir = args[5]; 

                // Get the name of the scenario to save... 
                string newxyfile = outdir + Path.GetFileNameWithoutExtension(oldxyfile) + "_" + args[0] + ".xy";

                // The data import file
                string ScenarioImportsFile = (args.Length == Headers.Length) ? args[6] : "";

                // List 
                return new HydroScenario(args[0], oldxyfile, newxyfile, Convert.ToDouble(args[1]), Convert.ToDouble(args[2]), Convert.ToInt32(args[3]), ScenarioImportsFile);
            }
            catch (Exception ex)
            {
                Console.WriteLine("\n\nError reading HydroScenario:\n" + ex.ToString()); 
                return null;
            }
        }

        public override string ToString()
        {
            return _name + Delims[0] 
                + this.HydropowerWeight.ToString() + Delims[0] 
                + this.Tolerance.ToString() + Delims[0] 
                + this.MaxIterations + Delims[0]
                + _oldxyfile + Delims[0]
                + Path.GetDirectoryName(Path.GetFullPath(_newxyfile)) + "\\" + Delims[0]
                + this.ScenarioImportsFile; 
        }

        #endregion 
    }
}
